home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / Graphics / Icons / next-icon@gun.com / Apps / ImagePortfolio / PaletteMatrix.cxx < prev    next >
Text File  |  1993-06-03  |  23KB  |  917 lines

  1. // -------------------------------------------------------------------------------------
  2. // PaletteMatrix.m
  3. // -------------------------------------------------------------------------------------
  4. // Permission is granted to freely redistribute this source code, and to use fragments
  5. // of this code in your own applications if you find them to be useful.  This class,
  6. // along with the source code, come with no warranty of any kind, and the user assumes
  7. // all responsibility for its use.
  8. // -------------------------------------------------------------------------------------
  9.  
  10. extern "Objective-C" {
  11. #import <objc/objc.h>
  12. #import <appkit/appkit.h>
  13. #import <libc.h>
  14. #import <stdlib.h>
  15. #import <stdio.h>
  16. #import <string.h>
  17. #import <mach/mach.h>
  18. #import <dpsclient/wraps.h>
  19. #import <math.h>
  20. }
  21.  
  22. #import "ImagePortfolio.h"
  23. #import "ExpandImage.h"
  24. #import "Portfolio.h"
  25. #import "PaletteCell.h"
  26. #import "PaletteMatrix.h"
  27.  
  28. // -------------------------------------------------------------------------------------
  29. // misc constants/macros
  30. #define    prefVERSION            1.02
  31. #define CELLCLASS            [PaletteCell class]
  32. #define    newCELL                [protoCell copy]
  33. #define    VSPACER                2.0
  34. #define    freeLIST(L)            { if (L) { [[L freeObjects] free]; L = (id)nil; } }
  35. #define    dbgMARKER(X)        { printf("%s; %d...\n", sel_getName(_cmd), X); }
  36. #define    cpNIL                (char*)nil
  37.  
  38. // -------------------------------------------------------------------------------------
  39. // origin/size shortcuts
  40. #define    X                    origin.x
  41. #define    Y                    origin.y
  42. #define    W                    size.width
  43. #define    H                    size.height
  44.  
  45. // -------------------------------------------------------------------------------------
  46. static id                    paletteCellClass = (id)nil;
  47. static char                    *cutListPaths   = cpNIL;
  48. static int                    sigPasteboard   = -1;
  49. static char                    *imagePathType  = cpNIL;
  50. static id                    pasteFont       = (id)nil;
  51.  
  52. // -------------------------------------------------------------------------------------
  53. // static selectors
  54. extern "Objective-C" {
  55.     static SEL compareCellTitle = @selector(compareCellTitle::);
  56.     static SEL _updateCells = @selector(_updateCells);
  57. }
  58.  
  59. // -------------------------------------------------------------------------------------
  60. @implementation PaletteMatrix
  61.  
  62. // -------------------------------------------------------------------------------------
  63.  
  64. /* new instance */
  65. + newFrame:(const NXRect *)fRect
  66. {
  67.     return [[self allocFromZone:NXDefaultMallocZone()] initFrame:fRect];
  68. }
  69.  
  70. /* initialize instance */
  71. - initFrame:(const NXRect*)fRect
  72. {
  73.  
  74.     /* init globals */
  75.     if (!imagePathType) imagePathType = NXCopyStringBuffer("imagePathType");
  76.  
  77.     /* init super */
  78.     [super initFrame:fRect];
  79.     if (!paletteCellClass) paletteCellClass = CELLCLASS;
  80.  
  81.     /* init vars */
  82.     delegate = self;
  83.     pbCopyTiff = YES;
  84.     pbCopyPath = NO;
  85.     loadingCells = NO;
  86.     loadMutex = mutex_alloc();
  87.  
  88.     /* image list container */
  89.     imageList = [[[List alloc] initCount:1] empty];
  90.     deleteList = (id)nil;
  91.  
  92.     /* default protocell */
  93.     protoCell = [paletteCellClass new];
  94.     intercell.width  = VSPACER;
  95.     intercell.height = VSPACER;
  96.   
  97.     /* size */
  98.     [self setMode:NX_LISTMODE];
  99.     [self setEmptySelectionEnabled:YES];
  100.     [self setAutoscroll:YES];
  101.     [self setBackgroundGray:NX_LTGRAY];
  102.     [self setCellBackgroundGray:NX_LTGRAY];
  103.     [self setFlipped:YES];
  104.  
  105.     return self;
  106. }
  107.  
  108. /* free object */
  109. - free
  110. {
  111.     [imageList free];        // cells are freed by super
  112.     freeLIST(deleteList);
  113.     mutex_free(loadMutex);
  114.     return [super free];
  115. }
  116.  
  117. /* free all cells */
  118. - freeCells
  119. {
  120.     [self clearSelectedCell];
  121.     [self renewRows:0 cols:0];
  122.     [cellList freeObjects];
  123.     [imageList empty];
  124.     [self sizeToCells];
  125.     freeLIST(deleteList);
  126.     [window setDocEdited:YES];
  127.     return self;
  128. }
  129.  
  130. /* remove selected cells (return deleted list) */
  131. - freeSelectedCells
  132. {
  133.     int    i;
  134.     id    iCell, dList = [self selectedCellList];
  135.   
  136.     /* return if no selected cells */
  137.     if (!dList) return (id)nil;
  138.   
  139.     /* delete selected cells from matrix */
  140.     i = [dList count];
  141.     [self clearSelectedCell];
  142.     [self renewRows:0 cols:0];
  143.     mutex_lock(loadMutex);
  144.     while(i) { 
  145.         iCell = [dList objectAt:--i];
  146.         [imageList removeObject:iCell];
  147.         [cellList removeObject:iCell];
  148.         [iCell setDelegate:(id)nil];
  149.     }
  150.     mutex_unlock(loadMutex);
  151.     [self sizeToCells];
  152.     [window setDocEdited:YES];
  153.   
  154.     return dList;
  155. }
  156.   
  157. // -------------------------------------------------------------------------------------
  158. // set attributes
  159.   
  160. /* set delegate */
  161. - setDelegate:anObject
  162. {
  163.     delegate = anObject;
  164.     return self;
  165. }
  166.  
  167. /* set to font */
  168. - setFont:fontObj
  169. {
  170.     int i = [self cellCount];
  171.     [super setFont:fontObj];
  172.     [window disableDisplay];
  173.     while(i) [[cellList objectAt:--i] setFont:fontObj];
  174.     [protoCell setFont:fontObj];
  175.     conFlags.calcSize = YES;
  176.     [window reenableDisplay];
  177.     return self;
  178. }
  179.  
  180. - changeFont:sender
  181. {
  182.     [self setFont:[[FontManager new] convertFont:[self font]]];
  183.     return self;
  184. }
  185.  
  186. /* set preferences (THIS ASSUMES PROPER PREFERENCE BUFFER FORMAT) */
  187. - (BOOL)setPreferences:(char*)prefBuff returnRows:(int*)rows cols:(int*)cols
  188. {
  189.     NXSize    size;
  190.     float    ver, pointSize;
  191.     char    fontName[256];
  192.     id        o;
  193.     if (!prefBuff || !*prefBuff) return NO;
  194.     sscanf(prefBuff, "%f %f %f %d %d %s %f", &ver, 
  195.         &size.width, &size.height, cols, rows, fontName, &pointSize);
  196.     [self setCellSize:&size];
  197.     if (o = [Font newFont:fontName size:pointSize]) [self setFont:o];
  198.     return YES;
  199. }
  200.   
  201. // -------------------------------------------------------------------------------------
  202. // pasteboard
  203.  
  204. - (char*)imagePaths:list
  205. {
  206.     int        i, c, len = 0;
  207.     char    *fullPath, *p;
  208.     if (!(c = [list count])) return cpNIL;
  209.     for (i=0;i<c;i++) { len += strlen([[list objectAt:i] imagePath]) + 1; }
  210.     fullPath = p = (char*)malloc(len + 1);
  211.     for (i=0;i<c;i++) { sprintf(p, "%s\t", [[list objectAt:i] imagePath]); p += strlen(p); }
  212.     *(p - 1) = 0;        // remove trailing tab (p > fullPath is guaranteed)
  213.     return fullPath;
  214. }
  215.  
  216. /* copy listed cells to pasteboard */
  217. - copyToPasteboard:list paths:(char*)listPaths
  218. {
  219.     char        *dt[2], *data, *path;
  220.     int            len, max, n = 0;
  221.     NXStream    *stream;
  222.     NXImage        *image;
  223.     Pasteboard    *pb;
  224.     id            first;
  225.   
  226.     /* return if no items to copy */
  227.     if (![list count]) return self;
  228.   
  229.     /* return if no selected cell */
  230.     pb = [Pasteboard newName:NXGeneralPboard];
  231.     if (!pb) { NXLogError("ImagePortfolio: no pasteboard"); return self; }
  232.   
  233.     /* declare pasteboard types */
  234.     if (pbCopyPath) dt[n++] = (char*)NXAsciiPboardType;
  235.     if (pbCopyTiff) dt[n++] = (char*)NXTIFFPboardType;
  236.     dt[n++] = (char*)imagePathType;
  237.     [pb declareTypes:dt num:n owner:NXApp];
  238.     sigPasteboard = [pb changeCount];
  239.   
  240.     /* get first image */
  241.     first = [list objectAt:0];
  242.   
  243.     /* write image representation to pasteboard */
  244.     if (pbCopyTiff) {
  245.         stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  246.         image = [first image];
  247.         [image setName:[first cellTitle]];
  248.         [image lockFocus];
  249.         [image writeTIFF:stream];
  250.         [image unlockFocus];
  251.         NXGetMemoryBuffer(stream, &data, &len, &max);
  252.         [pb writeType:NXTIFFPboardType data:data length:len];
  253.         NXClose(stream);
  254.         NXFreeObjectBuffer(data, len);
  255.     }
  256.   
  257.     /* write image path name to pasteboard */
  258.     if (pbCopyPath && (path = (char*)[first imagePath])) {
  259.         [pb writeType:NXAsciiPboardType data:path length:strlen(path) + 1];
  260.     }
  261.  
  262.     /* write custom type */
  263.     if (listPaths) [pb writeType:imagePathType data:listPaths length:strlen(listPaths) + 1];
  264.  
  265.     return self;
  266. }
  267.  
  268. /* copy TIFF from pasteboard */
  269. - copyFromPasteboard
  270. {
  271.     id            pb, pSave, imageId = (id)nil;
  272.     char        **types, titleBuff[256], *tiffName = cpNIL;
  273.     char        *path = (char*)[NXApp lastPath];
  274.     NXStream    *stream;
  275.     
  276.     /* return if no pasteboard */
  277.     if (!(pb = [Pasteboard newName:NXGeneralPboard])) return (id)nil;
  278.  
  279.     /* check for custom type */
  280.     for (types = (char**)[pb types]; *types && strcmp(*types,imagePathType); types++);
  281.     if (*types) {
  282.         char *data;
  283.         int len;
  284.         if (![pb readType:imagePathType data:&data length:&len]) return (id)nil;
  285.         [delegate loadFileString:data :NO :NO];
  286.         [pb deallocatePasteboardData:data length:len];
  287.         return (id)nil;
  288.     }
  289.   
  290.     /* search pasteboard types for a TIFF file */
  291.     for (types=(char**)[pb types]; *types && strcmp(*types,(char*)NXTIFFPboardType); types++);
  292.     if (!*types) return (id)nil;
  293.   
  294.     /* read data from pasteboard */
  295.     if ((stream = [pb readTypeToStream:NXTIFFPboardType])   &&
  296.         (imageId = [[NXImage alloc] initFromStream:stream]) &&
  297.         ([imageId lastRepresentation])                         ) {
  298.      
  299.         /* display image */
  300.         sprintf(titleBuff, "%s - TIFF", ([imageId name]?[imageId name]:"NXImage"));
  301.         [[delegate expandImage] showImage:imageId:(char*)nil title:titleBuff];
  302.  
  303.         /* display save panel */
  304.         pSave = [SavePanel new];
  305.         [pSave setTitle:"Save TIFF file image"];
  306.         [pSave setPrompt:"File:"];
  307.         [pSave setRequiredFileType:"tiff"];
  308.         [pSave setDirectory:path];
  309.         if ([pSave runModalForDirectory:path file:""]) tiffName = (char*)[pSave filename];
  310.   
  311.         /* save to file */
  312.         if (tiffName) {
  313.             NXSaveToFile(stream, tiffName);
  314.             [delegate loadFileString:tiffName :NO :NO];
  315.         }
  316.   
  317.         /* free memory */
  318.         NXCloseMemory(stream, NX_FREEBUFFER);
  319.     
  320.     } else {
  321.         const char *e, *m, *c;
  322.         
  323.         /* show warning/error panel */
  324.         e = NXLocalizedString("Error", cpNIL, cpNIL);
  325.         m = NXLocalizedString("Unable to read TIFF from PasteBoard.", cpNIL, cpNIL);
  326.         c = NXLocalizedString("Continue", cpNIL, cpNIL);
  327.         NXRunAlertPanel(e, m, c, cpNIL, cpNIL);
  328.         
  329.     }
  330.   
  331.     /* free image */
  332.     [[delegate expandImage] clearImage];  
  333.     if (imageId) [imageId free];
  334.  
  335.     return self;
  336. }
  337.   
  338. // -------------------------------------------------------------------------------------
  339. // first responder
  340. #define    delKEY        0x7F
  341.  
  342. /* never relinquish firstResponder */
  343. - resignFirstResponder { return (id)nil; }
  344.  
  345. /* show preferences (menu connection should point here to allow disabling when necessary) */
  346. - showPreferences:sender { return [NXApp showPreferences:self]; }
  347.  
  348. /* perform close window functions */
  349. - performClose:sender { return [window performClose:sender]; }
  350. - performMiniaturize:sender { return [window performMiniaturize:sender]; }
  351.  
  352. /* accept keydown event (check for delete key) */
  353. - keyDown:(NXEvent*)e
  354. {
  355.     if (e->data.key.charCode == delKEY) return [self delete:(id)nil];
  356.     return [super keyDown:e];
  357. }
  358.  
  359. /* delete selected cells */
  360. - delete:sender
  361. {
  362.     freeLIST(deleteList);
  363.     deleteList = [self freeSelectedCells];
  364.     [self display];
  365.     return self;
  366. }
  367.  
  368. /* copy previously deleted cells back to imageList */
  369. - undelete:sender
  370. {
  371.     int    i, c;
  372.     id    iCell;
  373.     if (!deleteList) return self;
  374.     for (c = [deleteList count], i = 0; i < c; i++) {
  375.         iCell = [deleteList objectAt:i];
  376.         if ([self findCellWithImageFilePath:(char*)[iCell imagePath]]) continue;
  377.         [iCell setDelegate:delegate];
  378.         [self addImageCell:iCell];
  379.         [window setDocEdited:YES];
  380.     }
  381.     [deleteList free];
  382.     deleteList = (id)nil;
  383.     [self resizeAndDisplay];
  384.     return self;
  385. }
  386.  
  387. /* return true if undeletable */
  388. - (BOOL)undeletable
  389. {
  390.     return ([deleteList count])? YES : NO;
  391. }
  392.  
  393. /* cut selected cells */
  394. - cut:sender
  395. {
  396.     [self delete:sender];
  397.     if (!deleteList) return self;
  398.     if (cutListPaths) { free(cutListPaths); cutListPaths = cpNIL; }
  399.     cutListPaths = [self imagePaths:deleteList];
  400.     [self copyToPasteboard:deleteList paths:cutListPaths];
  401.     return self;
  402. }
  403.  
  404. /* make a copy of the selected cells */
  405. - copy:sender
  406. {
  407.     id    dList = [self selectedCellList];
  408.     if (!dList) return self;
  409.     if (cutListPaths) { free(cutListPaths); cutListPaths = cpNIL; }
  410.     cutListPaths = [self imagePaths:dList];
  411.     [self copyToPasteboard:dList paths:cutListPaths];
  412.     [dList free];
  413.     return self;
  414. }
  415.  
  416. /* copy cut cells back to imageList */
  417. - paste:sender
  418. {
  419.     if ([[Pasteboard new] changeCount] != sigPasteboard) [self copyFromPasteboard];
  420.     else [delegate loadFileString:cutListPaths :NO :NO];
  421.     [self resizeAndDisplay];
  422.     return self;
  423. }
  424.  
  425. /* select all cells */
  426. - selectAll:sender
  427. {
  428.     int    i;
  429.     if (mFlags.radioMode) return self;
  430.     i = [self imageListCount];
  431.     while(i) [[self imageAt:--i] setState:1];
  432.     [self display];
  433.     return self;
  434. }
  435.  
  436. /* sort images by name */
  437. - sortByCellTitle:sender
  438. {
  439.     [self shellSort:compareCellTitle];
  440.     [self resizeAndDisplay];
  441.     return self;
  442. }
  443.  
  444. /* copy font */
  445. - copyFont:sender
  446. {
  447.     pasteFont = [self font];
  448.     return self;
  449. }
  450.  
  451. /* paste font */
  452. - pasteFont:sender
  453. {
  454.     if (pasteFont) [self setFont:pasteFont];
  455.     return self;
  456. }
  457.  
  458. // -------------------------------------------------------------------------------------
  459. // first responder: save 
  460.  
  461. - (char*)getPreferenceString:(char*)buff
  462. {
  463.     int    rows, cols;
  464.     [delegate getDisplayedRows:&rows cols:&cols];
  465.     sprintf(buff, "%.2f %.0f %.0f %d %d %s %.0f", prefVERSION, 
  466.         cellSize.width, cellSize.height, cols, rows, [font name], [font pointSize]);
  467.     return buff;
  468. }
  469.  
  470. /* save file names */
  471. - save:sender
  472. {
  473.     if ([self imageListCount]) return [delegate save:sender];
  474.     return self;
  475. }
  476. - saveAs:sender
  477. {
  478.     if ([self imageListCount]) return [delegate saveAs:sender];
  479.     return self;
  480. }
  481.  
  482. /* save to file (called by delegate) */
  483. - saveToFile:(char*)fileName
  484. {
  485.     int        i, c = [self imageListCount];
  486.     char    buff[512];
  487.     FILE    *fNum;
  488.   
  489.     /* get file name to save */
  490.     if (!fileName || !c) return (id)nil;
  491.   
  492.     /* open file */
  493.     fNum = fopen(fileName, "w");
  494.     if (!fNum) return (id)nil;
  495.  
  496.     /* write preferences and path names */
  497.     fprintf(fNum, ": %s\n", [self getPreferenceString:buff]); 
  498.     for (i = 0; i < c; i++) fprintf(fNum, "%s\n", [[self imageAt:i] imagePath]);
  499.   
  500.     /* close file and indicate that window has been saved */
  501.     fclose(fNum);
  502.     [window setDocEdited:NO];
  503.  
  504.     return self;
  505. }
  506.  
  507. // -------------------------------------------------------------------------------------
  508. // size
  509.  
  510. /* set intercell size */
  511. - setIntercell:(const NXSize*)aSize
  512. {
  513.     [super setIntercell:aSize];
  514.     [self sizeToCells];
  515.     return self;
  516. }
  517.   
  518. /* set image size */
  519. - setCellSize:(const NXSize*)size
  520. {
  521.     int    i = [self cellCount];
  522.  
  523.     /* update cell size */
  524.     [super setCellSize:size];
  525.   
  526.     /* notify cells of size change */
  527.     while(i) [[cellList objectAt:--i] setCellSize:size];
  528.     [protoCell setCellSize:size];
  529.   
  530.     /* recalc sizes */
  531.     [self sizeToCells];
  532.   
  533.     return self;
  534. }
  535.  
  536. /* readjust matrix to fit cells */
  537. - sizeToCells
  538. {
  539.     int        i, rows, cols, cnt;
  540.     id        oldCell;
  541.  
  542.     /* clear selected cells */
  543.     [self clearSelectedCell];
  544.   
  545.     /* adjust rows/cols */
  546.     cnt = [self imageListCount];
  547.     cols = (int)((frame.size.width + intercell.width) / (cellSize.width + intercell.width));
  548.     if (cols < 1) cols = 1;
  549.     rows = (cnt + cols - 1) / cols;
  550.     if (rows < 1) rows = 1;
  551.     [self renewRows:rows cols:cols];
  552.   
  553.     /* replace cells (and free old cells) */
  554.     for (i = 0; i < cnt; i++) {
  555.         oldCell = [cellList replaceObjectAt:i with:[self imageAt:i]];
  556.         if (oldCell && ![oldCell image]) [oldCell free];
  557.     }
  558.   
  559.     /* now resize matrix */
  560.     [super sizeToCells];
  561.   
  562.     return self;
  563. }
  564.  
  565. /* resize matrix and redisplay */
  566. - resizeAndDisplay
  567. {
  568.     [self sizeToCells];
  569.     [self display];
  570.     return self;
  571. }
  572.  
  573. /* return cellSize */
  574. - (NXSize*)cellSize
  575. {
  576.     return &cellSize;
  577. }
  578.  
  579. /* return intercellSize */
  580. - (NXSize*)intercell
  581. {
  582.     return &intercell;
  583. }
  584.  
  585. // -------------------------------------------------------------------------------------
  586. // add cell methods
  587.  
  588. /* called by main thread when cell was added */
  589. - _updateCells
  590. {
  591.     [self resizeAndDisplay];
  592.     loadingCells = NO;
  593.     return self;
  594. }
  595.  
  596. /* add new image */
  597. - addImage:(const char*)filePath
  598. {
  599.     id    iCell;
  600.   
  601.     /* see if its already in the list */
  602.     if ([self findCellWithImageFilePath:(char*)filePath]) return (id)nil;
  603.   
  604.     /* add and show cell */
  605.     iCell = newCELL;
  606.     if (![iCell setImageFile:filePath]) { [iCell free]; return (id)nil; }
  607.     [iCell setDelegate:delegate];
  608.     [self addImageCell:iCell];
  609.     if (!loadingCells) {
  610.         loadingCells = YES;
  611.         [self mainThreadPerform:_updateCells wait:NO];
  612.     }
  613.   
  614.     return iCell;
  615. }
  616.  
  617. /* return current number of image cells */
  618. - (int)imageListCount
  619. {
  620.     int    cnt;
  621.     mutex_lock(loadMutex);
  622.     cnt = [imageList count];
  623.     mutex_unlock(loadMutex);
  624.     return cnt;
  625. }
  626.  
  627. /* return indexed image cell */
  628. - imageAt:(int)index
  629. {
  630.     id    iCell;
  631.     mutex_lock(loadMutex);
  632.     iCell = [imageList objectAt:index];
  633.     mutex_unlock(loadMutex);
  634.     return iCell;
  635. }
  636.  
  637. /* add image cell */
  638. - addImageCell:iCell
  639. {
  640.     mutex_lock(loadMutex);
  641.     [imageList addObject:iCell];
  642.     mutex_unlock(loadMutex);
  643.     return iCell;
  644. }
  645.  
  646. // -------------------------------------------------------------------------------------
  647. // cell selection
  648.  
  649. /* drag image off of window */
  650. - _dragImage:(NXPoint*)mousePt:(NXEvent*)e
  651. {
  652.     NXPoint    pt = *mousePt;
  653.     int        row, col;
  654.     id        cellId;
  655.     NXRect rect = { { 0.0, 0.0 }, {48.0, 48.0 } };
  656.     
  657.     /* get cell/row/col */
  658.     cellId = [self getRow:&row andCol:&col forPoint:&pt];
  659.     if (!cellId || ![cellId imagePath]) return self;
  660.     
  661.     /* message window server for drag */
  662.     rect.origin.x = pt.x - rect.size.width  / 2.0;
  663.     rect.origin.y = pt.y - rect.size.height / 2.0;
  664.     [delegate _unregisterWindow];
  665.     [self dragFile:[cellId imagePath] fromRect:&rect slideBack:YES event:e];
  666.     [delegate _registerWindow];
  667.     
  668.     return self;
  669. }
  670.  
  671. /* drag cell around matrix */
  672. #define DRAGMASK (NX_MOUSEDRAGGEDMASK | NX_LMOUSEUPMASK)
  673. - _dragCell:(NXPoint*)mousePt:(NXEvent*)e
  674. {
  675.     NXPoint    pt = *mousePt, lastPt;
  676.     int        oldMask, row, col;
  677.     float    minX, maxX, minY, maxY;
  678.     NXSize    half;
  679.     NXRect    viewFrame, cellFrame, lastFrame, firstFrame, drawFrame;
  680.     id        cellId, tempView, cellCache;
  681.     
  682.     /* get/select cell */
  683.     [self clearSelectedCell];
  684.     cellId = [self getRow:&row andCol:&col forPoint:&pt];
  685.     if (!cellId || ![cellId imagePath]) return self;
  686.     [self selectCellAt:row:col];
  687.     [self display];
  688.     
  689.     /* setup mask for dragging */
  690.     oldMask = [window addToEventMask:DRAGMASK];
  691.     
  692.     /* make local image copy of cell */
  693.     [self getCellFrame:&cellFrame at:row:col];
  694.     viewFrame = cellFrame;
  695.     viewFrame.X = viewFrame.Y = 0.0;
  696.     half.width  = floor(viewFrame.W / 2.0);
  697.     half.height = floor(viewFrame.H / 2.0);
  698.     cellCache = [[NXImage alloc] initSize:&viewFrame.size];
  699.     [cellCache setDataRetained:YES];
  700.     [cellCache setFlipped:YES];
  701.     [cellCache lockFocus];
  702.     tempView = [[[View alloc] initFrame:&viewFrame] setFlipped:YES];
  703.     [cellId drawSelf:&viewFrame inView:[[window contentView] addSubview:tempView]];
  704.     [tempView free];
  705.     [cellCache unlockFocus];
  706.  
  707.     /* set cell drag mode */
  708.     [cellId setDragMode:YES];
  709.     [self display];
  710.     
  711.     /* get drag boundaries */
  712.     [self getCellFrame:&firstFrame at:0:0];
  713.     minX = firstFrame.X - half.width;
  714.     minY = firstFrame.Y + firstFrame.H - half.height;
  715.     [self getCellFrame:&lastFrame at:(numRows-1):(numCols-1)];
  716.     maxX = lastFrame.X + lastFrame.W - half.width;
  717.     maxY = lastFrame.Y + lastFrame.H + half.height;
  718.  
  719.     /* drag cell image */
  720.     [self lockFocus];
  721.     lastPt.x = cellFrame.X;
  722.     lastPt.y = cellFrame.Y + viewFrame.H;
  723.     drawFrame = cellFrame;
  724.     [cellCache composite:NX_COPY toPoint:&lastPt];
  725.     [window flushWindow];
  726.     for (;;) {
  727.         NXEvent    *ev = [NXApp getNextEvent:DRAGMASK], peek;
  728.         while ([NXApp peekNextEvent:DRAGMASK into:&peek]) ev = [NXApp getNextEvent:DRAGMASK];
  729.         [self autoscroll:ev];
  730.         lastPt = ev->location;
  731.         [self convertPoint:&lastPt fromView:(id)nil];
  732.         lastPt.x = (numCols > 1)? (lastPt.x - half.width) : 0.0;
  733.         lastPt.y += half.height;
  734.         if (lastPt.x < minX) lastPt.x = minX;
  735.         if (lastPt.x > maxX) lastPt.x = maxX;
  736.         if (lastPt.y < minY) lastPt.y = minY;
  737.         if (lastPt.y > maxY) lastPt.y = maxY;
  738.         if (ev->type == NX_LMOUSEUP) break;
  739.         cellFrame.X = lastPt.x;
  740.         cellFrame.Y = lastPt.y - viewFrame.H;
  741.         NXUnionRect(&cellFrame, &drawFrame);
  742.         PSgsave();
  743.         NXRectClip(&drawFrame);
  744.         [self drawSelf:&drawFrame : 1];
  745.         [cellCache composite:NX_COPY toPoint:&lastPt];
  746.         [window flushWindow];
  747.         PSgrestore();
  748.         drawFrame = cellFrame;
  749.     }
  750.     [self unlockFocus];
  751.     [window setEventMask:oldMask];
  752.     [cellId setDragMode:NO];
  753.     [cellCache free];
  754.  
  755.     /* find insert point */
  756.     NXPoint targetPt = lastPt;
  757.     targetPt.x += viewFrame.W;
  758.     if (numCols > 1) targetPt.y -= half.height;
  759.     int nRow = (int)floor((targetPt.y + intercell.height) / (viewFrame.H + intercell.height));
  760.     int nCol = (int)floor((targetPt.x + intercell.width ) / (viewFrame.W + intercell.width ));
  761.     if (nRow < 0) nRow = 0;
  762.     if ((nCol < 0) || (numCols == 1)) nCol = 0;
  763.     
  764.     /* re-arrange cells */
  765.     if ((row != nRow) || (col != nCol)) {
  766.         
  767.         /* find end of valid cells */
  768.         int oldNdx = (row * numCols) + col;
  769.         int newNdx = (nRow * numCols) + nCol;
  770.         if (newNdx > (int)[cellList count]) newNdx = [cellList count];
  771.         for (;(newNdx > 0) && ![[cellList objectAt:newNdx - 1] image]; newNdx--);
  772.         
  773.         /* move cell */
  774.         if (newNdx != (oldNdx + 1)) {
  775.             [imageList insertObject:self at:newNdx];    // use 'self' as placeholder
  776.             [imageList removeObject:cellId];
  777.             [imageList replaceObject:self with:cellId];
  778.             [self sizeToCells];    // rebuild/refresh 'cellList'
  779.         }
  780.         
  781.     }
  782.     
  783.     /* redisplay matrix */
  784.     [self selectCell:cellId];
  785.     [self display];
  786.     
  787.     /* return */
  788.     return self;
  789.  
  790. }
  791. #undef DRAGMASK
  792.  
  793. /* intercept mouse down event */
  794. - mouseDown:(NXEvent*)e
  795. {
  796.     
  797.     /* ignore while images are being loaded into palette */
  798.     if ([delegate isLoading]) return self;
  799.  
  800.     /* convert point to view */
  801.     NXPoint pt = e->location;
  802.     [self convertPoint:&pt fromView:(id)nil];
  803.     
  804.     /* Alternate-Shift */
  805.     if ((e->flags) & NX_ALTERNATEMASK) return [self _dragImage:&pt:e];
  806.     
  807.     /* Command-Shift */
  808.     if ((e->flags) & NX_COMMANDMASK) return [self _dragCell:&pt:e];
  809.     
  810.     /* return regular mouse-down event */
  811.     return [super mouseDown:e];
  812.     
  813. }
  814.  
  815. /* return true if there are any selectable cells */
  816. - (BOOL)anySelectableCells
  817. {
  818.     return [self imageListCount]? YES : NO;
  819. }
  820.  
  821. /* find image with name */
  822. - findCellWithImageFilePath:(char*)p
  823. {
  824.     int    i;
  825.     id    o, f = (id)nil;
  826.     i = [self imageListCount];
  827.     while(i) if (!strcmp([(o=[self imageAt:--i]) imagePath], p)) { f = o; break; }
  828.     return f;
  829. }
  830.  
  831. /* return list of selected cells */
  832. - (int)selectedCellCount
  833. {
  834.     int    c = 0, i;
  835.     i = [self imageListCount];
  836.     while(i) if ([[self imageAt:--i] isSelected]) c++;
  837.     return c;
  838. }
  839.  
  840. /* return first selected cell found */
  841. - selectedCell
  842. {
  843.     int    i;
  844.     id    c, r = (id)nil;
  845.     i = [self imageListCount];
  846.     while(i) if ([(c = [self imageAt:--i]) isSelected]) r = c;
  847.     return r;
  848. }
  849.   
  850. /* return list of selected cells */
  851. - selectedCellList
  852. {
  853.     int    i;
  854.     id    c, list = listALLOC(2);
  855.     i = [self imageListCount];
  856.     while(i) if ([(c=[self imageAt:--i]) isSelected]) [list addObject:c];
  857.     if (![list count]) { [list free]; list = (id)nil; }
  858.     return list;
  859. }
  860.  
  861. /* return string containing file names of all selected cells */
  862. - (char*)selectedCellPaths
  863. {
  864.     id        list = [self selectedCellList];
  865.     char    *fullPath = [self imagePaths:list];
  866.     [list free];
  867.     return fullPath;
  868. }
  869.  
  870. // -------------------------------------------------------------------------------------
  871. // sort compare methods
  872.  
  873. /* sort/compare by image name */
  874. - (int)compareCellTitle:image1:image2
  875. {
  876.     return strcasecmp([image1 cellTitle], [image2 cellTitle]);
  877. }
  878.  
  879. // -------------------------------------------------------------------------------------
  880. // sort images
  881.  
  882. /* indexed from 1 */
  883. #define    _COMPARE(I, J)    ((int)[self perform:sortCompare with:(I) with:(J)])
  884. #define    _ELEM(i)        [imageList objectAt:(i) - 1]
  885. #define    _setELEM(i, V)    [imageList replaceObjectAt:(i) - 1 with:(V)]
  886.  
  887. /* shell sort: O(N ^ 1.5) */
  888. - shellSort:(SEL)sortCompare
  889. {
  890.     int    i, j, h, N;
  891.     id    elem, temp;
  892.   
  893.     /* check for invalid compare method */
  894.     if (!sortCompare) return (id)nil;
  895.  
  896.     /* lock imageList */
  897.     mutex_lock(loadMutex);
  898.  
  899.       /* determine h: (1 + 3 + 3^2 + 3^3 + 3^4 + ...) [ h = pow(3,floor(log(2*N+1)/log(3))) ]*/
  900.     for (N = [imageList count], h = 1; h <= N; h = 3 * h + 1);
  901.   
  902.     /* shell sort */
  903.     while (h > 1)
  904.     for (h /= 3, i = h + 1; i <= N; i++) {
  905.         elem = _ELEM(i);
  906.         for (j=i; (j>h) && (_COMPARE((temp=_ELEM(j-h)),elem)>0); j-=h) _setELEM(j, temp);
  907.         _setELEM(j, elem);
  908.     }
  909.  
  910.     /* unlock imageList */
  911.     mutex_unlock(loadMutex);
  912.  
  913.     return self;
  914. }
  915.  
  916. @end
  917.